﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TFDP.Common;
using Microsoft.Data.Schema.SchemaModel;
using Microsoft.VisualStudio.Data.Schema.Package.Project;
using TFDP.Common.T4;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel;
using Microsoft.Data.Schema;
using TFDP.Common.Configuration;

namespace TFDP.Processing
{
    [Export(typeof(IProcessor))]
    public class Processor : IProcessor
    {
        private ThreadsafeQueue<IModelElement> workItems;
        private LoodswezenProperties loodswezenProperties;
        private ThreadsafeQueue<GeneratedArtifact> generatedItems;
        private bool isRunning;
        private bool isBusy;
        private string projectName;
        private UserInteractionServices interactionServices;
        private SettingsCollection settings;

        [ImportMany(typeof(IGenerator))]
        public IEnumerable<IGenerator> Generators { get; private set; }

        public Processor()
        {
            workItems = new ThreadsafeQueue<IModelElement>();
            generatedItems = new ThreadsafeQueue<GeneratedArtifact>();
            settings = new SettingsCollection()
            {
                new Setting<string>() { Key = "TablePathFormat", Value = "" },
                new Setting<string>() { Key = "TriggerPathFormat", Value = "" },
                new Setting<string>() { Key = "ProcedurePathFormat", Value = "" }
            };
        }

        #region IProcessor Members

        public int Count
        {
            get { return workItems.Count; }
        }

        public IEnumerable<Type> SupportedTypes
        {
            get
            {
                var supportedTypes = from g in this.Generators
                                     select g.SupportedType;

                return supportedTypes.Distinct().ToList<Type>();
            }
        }

        public IExtenderType PropertyExtenderType
        {
            get
            {
                if (loodswezenProperties == null)
                    loodswezenProperties = new LoodswezenProperties();

                return loodswezenProperties;
            }
        }

        public ThreadsafeQueue<IModelElement> Queue
        {
            get { return this.workItems; }
        }

        public bool IsRunning
        {
            get { return isRunning; }
        }

        public bool IsStopped
        {
            get { return !isRunning; }
        }

        public bool IsBusy
        {
            get { return isBusy; }
        }

        public string ProjectName
        {
            get { return projectName; }
            private set
            {
                if (projectName != value)
                {
                    projectName = value;

                    RaisePropertyChanged("ProjectName");
                }
            }
        }

        public SettingsCollection Settings
        {
            get { return settings; }
        }

        public string GetTypeName(IModelElement element)
        {
            if (element == null)
                throw new ArgumentNullException("element");

            return this.interactionServices.GetElementTypeDescription(element.ElementClass);
        }

        public void Initialize(Microsoft.VisualStudio.Data.Schema.Package.Project.IDatabaseProjectNode project)
        {
            this.interactionServices = project.UserInteractionServices;

            ProjectName = project.Caption;

            DatabaseTemplateProcessor.Instance.TemplateDirectory = project.FindTemplateDirectory();

            DatabaseTemplateProcessor.Instance.ProjectId = project.Caption;

            // MEF Composition
            // Create a catalog for the assembly that we're part of
            var catalog = new AssemblyCatalog(this.GetType().Assembly);
            var container = new CompositionContainer(catalog);

            container.ComposeParts(this);

            // Initialize the generators
            foreach (IGenerator generator in Generators)
                generator.Initialize(this, project);

            // We can start now
            this.isRunning = false;
        }

        public void Enqueue(Microsoft.Data.Schema.SchemaModel.IModelElement element)
        {
            // Only enqueue an element if it is not already included
            // in the workItems queue
            var matches = from e in this.workItems.ToList()
                          where e.IsNameEqual(element)
                          select e;

            if (matches.Count() == 0)
            {
                this.workItems.Enqueue(element);

                RaiseQueueChanged();
            }
        }

        public GeneratedArtifact PopGenerationItem()
        {
            if (generatedItems.Count > 0)
                return generatedItems.Dequeue();
            else
                return null;
        }

        public event EventHandler<EventArgs> GenerationCompleted;

        private void RaiseGenerationCompleted()
        {
            if (GenerationCompleted != null)
                GenerationCompleted(this, EventArgs.Empty);
        }
        #endregion
        
        #region IDatabaseProjectIdleProcessor Members

        public void DoWork()
        {
            if (!isRunning || isBusy)
                return;

            while(workItems.Count > 0)
                ProcessWorkItems(0);
        }

        #endregion

        #region Processing
        /// <summary>
        /// Process the items in the queue.
        /// To process all the items in the queu specify count as 0
        /// </summary>
        /// <param name="count">The number of items to process. 0 to process all the items in the queue.</param>
        private void ProcessWorkItems(int count)
        {
            if (!isRunning)
                return;

            // Set busy
            isBusy = true;

            if (count == 0)
                count = workItems.Count;

            int current = 0;
            int generationCount = 0;

            // Keep processing until count items have been
            // processed or the queue is exhausted.
            while (current++ < count && workItems.Count > 0)
            {
                IModelElement item = workItems.Dequeue();
                
                RaiseQueueChanged();

                ProcessWorkItem(item);

                generationCount++;

                if (generationCount > 10)
                {
                    generationCount = 0;

                    RaiseGenerationCompleted();
                }
            }

            // Set no busy
            isBusy = false;

            if (generationCount > 0)
                RaiseGenerationCompleted();
        }

        /// <summary>
        /// Process a single workItem. Run all the generators that support
        /// the workItem type.
        /// </summary>
        /// <param name="workItem">An IModelElement instance to process</param>
        private void ProcessWorkItem(IModelElement workItem)
        {
            if (!isRunning)
                throw new InvalidOperationException("We're not running and you should not be here!");

            foreach (IGenerator generator in GeneratorsForType(workItem.GetType()))
            {
                try
                {
                    IEnumerable<GeneratedArtifact> items = generator.Generate(workItem);

                    foreach (GeneratedArtifact item in items)
                        generatedItems.Enqueue(item);
                }
                catch (Exception ex)
                {
                    Logger.LogError("Generation failed for " + generator.GetType().Name + ": " + ex.Message);
                }
            }
        }

        /// <summary>
        /// Find all the generators that support the given type.
        /// </summary>
        /// <param name="targetType">The type to support</param>
        /// <returns>A List<GeneratorBase> with the supporting generators</returns>
        private List<IGenerator> GeneratorsForType(Type targetType)
        {
            var matches = from g in this.Generators
                          where targetType.GetInterface(g.SupportedType.Name) != null
                          select g;

            return matches.ToList<IGenerator>();
        }
        #endregion

        #region IProcessor Members
        public event EventHandler<EventArgs> BeginProcessing;

        private void RaiseBeginProcessing()
        {
            if (BeginProcessing != null)
                BeginProcessing(this, EventArgs.Empty);
        }

        public event EventHandler<EventArgs> ProcessingComplete;

        private void RaiseProcessingComplete()
        {
            if (ProcessingComplete != null)
                ProcessingComplete(this, EventArgs.Empty);
        }

        public event EventHandler<EventArgs> QueueChanged;

        private void RaiseQueueChanged()
        {
            if (QueueChanged != null)
                QueueChanged(this, EventArgs.Empty);
        }

        public void Start()
        {
            if (!this.isRunning)
            {
                this.isRunning = true;
                this.isBusy = false;
                RaisePropertyChanged("IsRunning");
                RaisePropertyChanged("IsStopped");
            }
        }

        public void Stop()
        {
            if (this.isRunning)
            {
                this.isRunning = false;
                this.isBusy = false;
                RaisePropertyChanged("IsRunning");
                RaisePropertyChanged("IsStopped");
            }
        }
        #endregion

        public event PropertyChangedEventHandler PropertyChanged;

        private void RaisePropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
        }
    }
}
